package tools

{
    //像素级精确碰撞算法优化
    //代码：Aone
    //2009-10-30
    import flash.display.BitmapData;
    import flash.display.BlendMode;
    import flash.display.DisplayObject;
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
    /**
       原先的BitmapData之间精确判断顺序是这样的。
       1。判断2容器矩形是否有重叠
       2。有重叠则取两个容器的重叠部分draw到一个新BitmapData中并且把有色彩的部分全部纯色化，A容器放进去后B容器以一个滤镜模式再绘制进去。
       3。判断该容器是否有A容器的颜色和B容器的颜色经过滤镜后产生的颜色。有就表示有碰撞，无则无碰撞。
    
       优化的判断顺序如下：
       1。判断2容器矩形是否有重叠
       2。计算2容器的尺寸大小缩小到20*20需要缩小多少倍率，同时根据这个倍率重新计算重叠部分，也就是缩小后的重叠部分。
       3。向一个20*20的BitmapData中绘制2个容器对象，经过一系列计算获取碰撞部分。
       4。这时的重叠部分是缩小后图像的重叠部分，实际尺寸这个区域内可能并未碰撞，所以在压缩检测时有碰撞就需要进行再次检测。而检测时候把之前的倍率重新计算，计算原理是：由于获得碰撞区域一般都比原先矩形重叠部分小，那么用计算得来的这个区域代替原来的矩形重叠区域重新计算（跳到第2步）。
       5。一直到缩小倍率降为1时仍然有碰撞，则说明2容器对象确实产生了碰撞。那么实际检测面积就是20*20*检测次数。（实际检测中就算测试对象尺寸6K*3K也很少会进行4次以上）
     * */
    public class BitmapHitTestPlus
    {
        
        public static var tileSize:int = 20
        
        public static function complexHitTestObject(target1:DisplayObject, target2:DisplayObject):Boolean
        {
            //横向缩小到tileSize尺寸需要的倍数，也就是期望检测的时候缩小到的尺寸。
            var scaleX:Number = (target1.width < target2.width ? target1.width : target2.width) / BitmapHitTestPlus.tileSize
            //纵向缩小到tileSize尺寸需要的倍
            var scaleY:Number = (target1.height < target2.height ? target1.height : target2.height) / BitmapHitTestPlus.tileSize
            //
            //如果倍数小于1则按原始倍率也就是原始尺寸
            scaleX = scaleX < 1 ? 1 : scaleX
            scaleY = scaleY < 1 ? 1 : scaleY
            //draw用point
            var pt:Point = new Point()
            //做2次draw时使用的颜色
            var ct:ColorTransform = new ColorTransform()
            ct.color = 0xFF00000F
            //原始尺寸下的重叠矩形                
            var oldHitRectangle:Rectangle = intersectionRectangle(target1, target2)
            //用于存放缩放的重叠矩形
            var hitRectangle:Rectangle = new Rectangle()
            return complexIntersectionRectangle(target1, target2, scaleX, scaleY, pt, ct, oldHitRectangle, hitRectangle, BitmapHitTestPlus.tileSize, BitmapHitTestPlus.tileSize).width != 0;
        
        }
        
        public static function intersectionRectangle(target1:DisplayObject, target2:DisplayObject):Rectangle
        {
            
            // If either of the items don't have a reference to stage, then they are not in a display list
            // or if a simple hitTestObject is false, they cannot be intersecting.
            if (!target1.root || !target2.root || !target1.hitTestObject(target2))
            {
                return new Rectangle();
            }
            
            // Get the bounds of each DisplayObject.
            
            var bounds1:Rectangle = target1.getBounds(target1.root);
            
            var bounds2:Rectangle = target2.getBounds(target2.root);
            
            // Determine test area boundaries.
            
            var intersection:Rectangle = new Rectangle();
            
            intersection.x = Math.max(bounds1.x, bounds2.x);
            
            intersection.y = Math.max(bounds1.y, bounds2.y);
            
            intersection.width = Math.min((bounds1.x + bounds1.width) - intersection.x, (bounds2.x + bounds2.width) - intersection.x);
            
            intersection.height = Math.min((bounds1.y + bounds1.height) - intersection.y, (bounds2.y + bounds2.height) - intersection.y);
            
            return intersection;
        }
        
        public static function complexIntersectionRectangle(target1:DisplayObject, target2:DisplayObject, scaleX:Number, scaleY:Number, pt:Point, ct:ColorTransform, oldhitRectangle:Rectangle, hitRectangle:Rectangle, nowW:int, nowH:int):Rectangle
        {
            if (!target1.hitTestObject(target2))
            {
                return new Rectangle();
            }
            //根据纵横的缩小倍数来计算缩小的重叠矩形尺寸
            hitRectangle.x = oldhitRectangle.x
            hitRectangle.y = oldhitRectangle.y
            hitRectangle.width = oldhitRectangle.width / scaleX
            hitRectangle.height = oldhitRectangle.height / scaleY
            //建立一个用来draw的临时BitmapData对象，尺寸为期望宽nowW,期望高nowH
            var bitmapData:BitmapData = new BitmapData(nowW, nowH, true, 0);
            //绘制对象1其缩放比例和位移量由getDrawMatrix（）函数计算，并且把不透明处绘制为ct的颜色
            bitmapData.draw(target1, BitmapHitTestPlus.getDrawMatrix(target1, hitRectangle, scaleX, scaleY), ct);
            //当纵横缩小比例不为1的时候把任何有色像素重新替换成ct的颜色0xFF00000F，原因为缩小的对象在进行纯色绘制的时候会有半透明像素产生，如果不加以重新替换会影响后面对象2的滤镜效果。
            if (scaleX != 1 && scaleY != 1)
            {
                bitmapData.threshold(bitmapData, bitmapData.rect, pt, ">", 0, 0xFF00000F)
            }
            //绘制对象2其缩放比例和位移量由getDrawMatrix（）函数计算，并且把不透明处绘制为ct的颜色，并且模式为叠加。如此一来两个对象重叠的部分颜色值必定大于0xFF00000F。
            bitmapData.draw(target2, BitmapHitTestPlus.getDrawMatrix(target2, hitRectangle, scaleX, scaleY), ct, BlendMode.ADD);
            //把所有颜色值大于0xFF00000F的部分（也就是重叠部分）重新替换为不透明红色方便后面getColorBoundsRect()方法计算尺寸。这里替换的原因是getColorBoundsRect()不支持范围取色而只支持单色计算。
            //对象1缩放后可以重新替换掉透明色，但是对象2则无法使用同一方法，但是对象2由于也是经过缩放绘制也会有半透明像素，那么重叠部分虽然全部大于0xFF00000F，但未必是统一的。
            var hits:int = bitmapData.threshold(bitmapData, bitmapData.rect, pt, ">", 0xFF00000F, 0xFFFF0000)
            //判断红色区域尺寸
            var intersection:Rectangle = bitmapData.getColorBoundsRect(0xFFFFFFFF, 0xFFFF0000);
            
            bitmapData = null
            //如果红色区域宽度不为0，即bitmapData中含有红色像素。此时说明对象1和对象2在此次判定中有重叠有碰撞
            if (intersection.width != 0)
            {
                //如果纵横缩放比例有任意一个不是原始尺寸
                if (scaleX > 1 || scaleY > 1)
                {
                    //并且红色像素的数量比较少，对象1和对象2的碰撞面积比较小的话
                    if (hits <= (scaleX + scaleY) * 1.5)
                    {
                        //由于bitmapData的宽高坐标都是以整数表示，那么经过缩放后取整的区域势必回又可能在取整的时候把真正可能产生碰撞的区域忽略。
                        //所以要进行下一次检测时候适当的把检测区域扩大xadd和yadd就是这个扩大的系数
                        var xadd:int = .5
                        var yadd:int = .5
                        //下次检测时候bitmapData的期望大小
                        var nextW:int = BitmapHitTestPlus.tileSize
                        var nextH:int = BitmapHitTestPlus.tileSize
                        //如果此次判定发现碰撞区域和bitmapData尺寸相同，那么在计算下次需要判断区域时候会和此次的区域相同，那么判断结果可能会和此次结果相同。这样则会引起堆栈上溢的情况，为了避免该情况发生，将缩小判断的尺寸扩大一倍进行再次检测。
                        if (intersection.width != nowW)
                        {
                            nextW = BitmapHitTestPlus.tileSize;
                        }
                        else
                        {
                            nextW = nowW * 2;
                        }
                        if (intersection.height != nowH)
                        {
                            nextH = BitmapHitTestPlus.tileSize;
                        }
                        else
                        {
                            nextH = nowH * 2;
                        }
                        //根据检测出来的缩的碰撞区域来计算未缩小的碰撞区域大小以方便下一次计算的时候缩小检测范围。
                        oldhitRectangle.x += (intersection.x - xadd) * scaleX
                        oldhitRectangle.y += (intersection.y - yadd) * scaleY
                        oldhitRectangle.width = (intersection.width + xadd * 2) * scaleX
                        oldhitRectangle.height = (intersection.height + yadd * 2) * scaleY
                        //根据检测期望缩小到的尺寸重新计算缩小倍率
                        scaleX = (oldhitRectangle.width / nextW)
                        scaleY = (oldhitRectangle.height / nextH)
                        //如果倍率小于2则直接按原始尺寸 
                        scaleX = scaleX < 2 ? 1 : scaleX
                        scaleY = scaleY < 2 ? 1 : scaleY
                        //进行下一次判定
                        intersection = complexIntersectionRectangle(target1, target2, scaleX, scaleY, pt, ct, oldhitRectangle, hitRectangle, nextW, nextH)
                    }
                }
            }
            return intersection;
        
        }
        
        protected static function getDrawMatrix(target:DisplayObject, hitRectangle:Rectangle, scaleX:Number, scaleY:Number):Matrix
        {
            var localToGlobal:Point;
            var matrix:Matrix;
            var rootConcatenatedMatrix:Matrix = target.root.transform.concatenatedMatrix;
            
            localToGlobal = target.localToGlobal(new Point());
            
            matrix = target.transform.concatenatedMatrix;
            
            matrix.tx = (localToGlobal.x - hitRectangle.x) / scaleX;
            
            matrix.ty = (localToGlobal.y - hitRectangle.y) / scaleY;
            
            matrix.a = matrix.a / rootConcatenatedMatrix.a / scaleX;
            matrix.d = matrix.d / rootConcatenatedMatrix.d / scaleY;
            return matrix;
        }
    }
}